package com.atguigu.aiproject.service.impl;

import com.atguigu.aiproject.config.RabbitMQConfig;
import com.atguigu.aiproject.constant.PromptBuilder;
import com.atguigu.aiproject.dto.CreationQueryDTO;
import com.atguigu.aiproject.dto.PublishPostDTO;
import com.atguigu.aiproject.dto.PublishRequestDTO;
import com.atguigu.aiproject.exception.BusinessException;
import com.atguigu.aiproject.mapper.CreationMapper;
import com.atguigu.aiproject.mapper.ImgMapper;
import com.atguigu.aiproject.entity.Creation;
import com.atguigu.aiproject.entity.Img;
import com.atguigu.aiproject.service.CreationService;
import com.atguigu.aiproject.vectorstore.CreationVectorStoreManager;
import com.atguigu.aiproject.utils.MappingUtils;
import com.atguigu.aiproject.utils.UserContext;
import com.atguigu.aiproject.vo.CreationCoverVO;
import com.atguigu.aiproject.vo.CreationDetailsVO;
import com.atguigu.aiproject.vo.PageResult;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.*;

import static com.atguigu.aiproject.constant.RedisKeyConstant.CREATION_DETAIL_KEY;


@Service
@Slf4j
public class CreationServiceImpl extends ServiceImpl<CreationMapper, Creation> implements CreationService {
    private final CreationMapper creationMapper;
    private final ImgMapper imgMapper;
    private final RedisTemplate<String, Object> redisTemplate;
    private final RabbitTemplate rabbitTemplate;

    private final String filePath;
    private final ChatClient creationSearchClient;
    private final CreationVectorStoreManager creationVectorStoreManager;

    public CreationServiceImpl(CreationMapper creationMapper, ImgMapper imgMapper,
                               RedisTemplate<String, Object> redisTemplate,
                               RabbitTemplate rabbitTemplate, @Value("${system.file_save_path}") String filePath,
                               ChatClient creationSearchClient, CreationVectorStoreManager creationVectorStoreManager) {
        this.creationMapper = creationMapper;
        this.imgMapper = imgMapper;
        this.redisTemplate = redisTemplate;
        this.rabbitTemplate = rabbitTemplate;
        this.filePath = filePath;
        this.creationSearchClient = creationSearchClient;
        this.creationVectorStoreManager = creationVectorStoreManager;
    }

    @Override
    public IPage<CreationCoverVO> searchCreationBySql(int pageNum, int pageSize, String sql) {
        // 创建分页对象
        return creationMapper.selectBySql(new Page<>(pageNum, pageSize), sql);
    }

    @Override
    public PageResult<CreationCoverVO> searchCreation(int pageNum, int pageSize, String searchPrompt) {
        log.info("searchPrompt: {}", searchPrompt);

        AiSearchResultDTO aiSearchResultDTO = creationSearchClient.prompt()
                .system(PromptBuilder.CREATION_SEARCH_PROMPT)
                .user(searchPrompt)
                .call()
                .entity(AiSearchResultDTO.class);
        List<Integer> creationIdList = aiSearchResultDTO.creationIdList;
        if (creationIdList.isEmpty()) {
            SearchRequest searchRequest = new SearchRequest.Builder()
                    .query(searchPrompt)
                    .similarityThreshold(0.45f)
                    .topK(150) // todo topK应设置为数据库所有作品数量
                    .build();
            List<Document> documents = creationVectorStoreManager.similaritySearch(searchRequest);
            for (Document document : documents) {
                String creationId = (String) document.getMetadata().get("作品ID");
                creationIdList.add(Integer.parseInt(creationId));
            }
            if(creationIdList.isEmpty())return null;
            Exception e = new BusinessException("AI发癫了");
            e.printStackTrace();
        }

        IPage<CreationCoverVO> creationCoverVOIPage = creationMapper.selectCreationCoverVOsByIds(new Page<>(pageNum, pageSize), creationIdList);
        UUID uuid = UUID.randomUUID();

        String redisKey = "creation:ai:covers:" + uuid;
        redisTemplate.opsForValue().set(redisKey, creationIdList, Duration.ofMinutes(30));

        return new PageResult<>(
                creationCoverVOIPage.getRecords(),
                (int) creationCoverVOIPage.getTotal(),
                (int) creationCoverVOIPage.getCurrent(),
                (int) creationCoverVOIPage.getSize(),
                (int) creationCoverVOIPage.getPages(),
                uuid.toString()
        );
    }

    @Override
    public PageResult<CreationCoverVO> searchCreationByUUid(int pageNum, int pageSize, String searchId) {

        String redisKey = "creation:ai:covers:" + searchId;
        if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) {
            List<Integer> creationIdList = (List<Integer>) redisTemplate.opsForValue().get(redisKey);
            IPage<CreationCoverVO> creationCoverVOIPage = creationMapper.selectCreationCoverVOsByIds(new Page<>(pageNum, pageSize), creationIdList);
            // Redis中有数据，直接返回
            return new PageResult<>(
                    creationCoverVOIPage.getRecords(),
                    (int) creationCoverVOIPage.getTotal(),
                    (int) creationCoverVOIPage.getCurrent(),
                    (int) creationCoverVOIPage.getSize(),
                    (int) creationCoverVOIPage.getPages(),
                    searchId
            );
        }
        return null;
    }

    /**
     * 获取创作详情
     */
    @Override
    public CreationDetailsVO getDetails(Integer creationId) {
        String detailKey = CREATION_DETAIL_KEY + creationId;

        // 1. 尝试从Redis Hash获取作品详情
        Map<Object, Object> detailMap = redisTemplate.opsForHash().entries(detailKey);
        if (!detailMap.isEmpty()) {
            CreationDetailsVO detail = MappingUtils.mapToCreationDetail(detailMap);// 将Map转换为CreationDetailsVO
            log.info("从Redis获取作品详情成功：{}", detail);
            return detail;
        }

        // 2. Redis中没有，从数据库查询
        CreationDetailsVO detail = creationMapper.selectCreationDetailsById(creationId);
        if (detail == null) {
            return null;
        }

        // 3. 写入Redis缓存（Hash结构）
        cacheCreationDetail(creationId, detail);
        return detail;
    }

    /**
     * 缓存作品详情到Redis（使用Hash结构）
     */
    private void cacheCreationDetail(Integer creationId, CreationDetailsVO detail) {
        String detailKey = CREATION_DETAIL_KEY + creationId;

        try {
            // 转换为Map存储到Hash
            Map<String, Object> detailMap = MappingUtils.creationDetailToMap(detail);// 将CreationDetailsVO转换为Map
            redisTemplate.opsForHash().putAll(detailKey, detailMap);
            // 设置过期时间
            redisTemplate.expire(detailKey, Duration.ofHours(2));

            log.debug("缓存作品详情成功: creationId={}", creationId);
        } catch (Exception e) {
            log.error("缓存作品详情失败: creationId={}", creationId, e);
        }
    }

    /**
     * 获取用户所有创作作品
     */
    @Override
    public IPage<CreationCoverVO> getCreationCoverVOListByUserId(int pageNum, int pageSize, int userId) {
        return creationMapper.getCreationCoverVOListByUserId(new Page<>(pageNum, pageSize), userId);
    }

    /**
     * 获取所有创作作品
     * TODO:访问量大，修改频繁，即不适合缓存，也不适合直接访问数据库
     */
    @Override
    public IPage<CreationCoverVO> getCreationCoverVOList(int pageNum, int pageSize, CreationQueryDTO queryDTO) {
        return creationMapper.getCreationCoverVOList(new Page<>(pageNum, pageSize), queryDTO);
    }

    /**
     * 保存文章到数据库
     */
    @Override
    public Integer saveCreation(PublishPostDTO publishPostDTO, Boolean isApproved) throws IOException {
        Creation creation = MappingUtils.mappingCreation(publishPostDTO);
        creation.setReviewStatus(isApproved ? 1 : 0);
        // 先保存creation，获取自增id
        this.save(creation);
        //获取封面,处理img表
        /*String coverImg = creation.getCoverImg();
        if (coverImg != null && !coverImg.isEmpty()) {
            // 保存到img表
            Img img = new Img();
            img.setImgName(coverImg);
            img.setCreationId(creation.getId());
            imgMapper.save(img);
        }*/
        // 获取aiImg,处理img表
        String ai_img_url = publishPostDTO.getAi_img_url();
        // 若存在ai生成的图片，将ai生成的图片保存到本地.将保存后的文件名存到数据库
        if (ai_img_url != null && !ai_img_url.isEmpty()) {
            // 按逗号分割URL字符串
            String[] urls = ai_img_url.split(",");
            for (String url : urls) {
                // 去除空格
                url = url.trim();
                if (!url.isEmpty()) {
                    String filename = saveImageFromUrl(url, filePath);
                    System.out.println("保存图片到本地目录：" + filename);
                    // 添加空值检查，只有在文件名不为空时才保存图片记录
                    if (filename != null && !filename.isEmpty()) {
                        Img img = new Img();
                        img.setImgName(filename);
                        img.setCreationId(creation.getId()); // 此时可以获取到自增id
                        imgMapper.save(img);
                    }
                }
            }
        }
        return creation.getId();
    }

    /*
    发布文章到队列
    ai图片只存储地址，暂不保存
    封面图片保存到本地
     */
    @Override
    public void publishToQueue(PublishRequestDTO publishRequestDTO, MultipartFile pic) throws IOException {
        PublishPostDTO publishPostDTO = new PublishPostDTO();
        BeanUtils.copyProperties(publishRequestDTO, publishPostDTO);
        String fileName = "";
        //存在用户上传图片，先保存到本地
        if (pic != null && !pic.isEmpty()) {
            // 将上传的图片文件保存到指定目录下, 文件名用当前时间戳+文件名
            fileName = new Date().getTime() + pic.getOriginalFilename();
            publishPostDTO.setCoverImg(fileName);
            // 确保目录存在
            File directory = new File(filePath);
            if (!directory.exists()) {
                directory.mkdirs(); // 创建目录
            }
            File destFile = new File(directory, fileName);
            System.out.println("保存图片到本地目录：" + destFile.getAbsolutePath());
            pic.transferTo(new File(destFile.getAbsolutePath()));
        }
        //设置用户id
        publishPostDTO.setUserId(UserContext.getUserId());
        //将PublishPostDTO对象保存到队列中
        rabbitTemplate.convertAndSend(RabbitMQConfig.DIRECT_EXCHANGE_NAME, RabbitMQConfig.DIRECT_ROUTING_KEY, publishPostDTO);
    }

    /**
     * 从指定URL下载图片并保存到本地文件夹
     *
     * @param imageUrl 要下载的图片URL地址
     * @param dir      图片保存的本地目录路径
     * @return 保存后的文件名，如果下载失败则返回null
     * @throws IOException 当发生IO异常时抛出
     */
    private String saveImageFromUrl(String imageUrl, String dir) throws IOException {
        // 创建Java原生HttpClient实例，用于发送HTTP请求
        HttpClient httpClient = HttpClient.newHttpClient();

        // 构建HTTP GET请求，指定要下载图片的URL
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(imageUrl))  // 将字符串URL转换为URI对象
                .build();                   // 构建请求对象

        try {
            // 发送HTTP请求并指定响应体处理器为字节数组处理器
            HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());

            // 检查HTTP响应状态码是否为成功状态(200-299)
            if (response.statusCode() >= 200 && response.statusCode() < 300) {
                // 获取响应体中的图片字节数据
                byte[] imageBytes = response.body();

                // 从URL中提取文件名（去掉查询参数部分，获取最后一个/后的文件名）
                String fileName = imageUrl.split("\\?")[0].substring(imageUrl.lastIndexOf("/") + 1);

                // 构建图片保存的完整路径
                Path imagePath = Paths.get(dir, fileName);

                // 确保目录存在
                Files.createDirectories(imagePath.getParent());

                // 将图片字节数据写入到指定文件路径
                Files.write(imagePath, imageBytes);

                // 返回保存的文件名
                return fileName;
            }
            // 如果响应状态码不是成功状态，返回null表示下载失败
            return null;
        } catch (InterruptedException e) {
            // 如果请求被中断，将中断异常转换为IO异常抛出
            throw new IOException("下载图片被中断", e);
        }
    }

    public record AiSearchResultDTO(
            @JsonPropertyDescription("作品ID列表")
            List<Integer> creationIdList
    ) {}

}
